/*
 * Last review by   : Yaser
 * Last review date : JUL-25-09 
 */
using System;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Xml;
using ERP.Framework.Components.Utilities.TracingHelper;

namespace ERP.Framework.Components.LoggingHelper.ErrorLog
{
    /// <summary>
    /// Log using XML format
    /// </summary>
    public class XMLErrorLog : IErrorLog
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="XMLErrorLog"/> class.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        public XMLErrorLog(string filePath)
        {
            FilePath = filePath;
            FilePathType = PathType.Absolute;
            Type = ErrorLogType.XML;
        }

        #endregion

        #region Members

        private const string TRACING_CATEGORY = "ERP.Framework.Components.LoggingHelper.ErrorLog.XMLErrorLog";
        private const string XML_ROOT = "Exceptions";
        private const string XML_VERSION = "1.0";
        private const string XML_ENCODING = "utf-8";
        private const string ERROR_NODE = "Exception";
        /// <summary>
        /// Error message column name.
        /// </summary>
        public const string ERROR_MESSAGE = "ErrorMessage";
        /// <summary>
        /// Error source column name.
        /// </summary>
        public const string ERROR_SOURCE = "Source";
        /// <summary>
        /// Error stack trace column name.
        /// </summary>
        public const string ERROR_STACK_TRACE = "StackTrace";
        /// <summary>
        /// Error target site column name.
        /// </summary>
        public const string ERROR_TARGET_SITE = "TargetSite";
        /// <summary>
        /// Error id column name.
        /// </summary>
        public const string ERROR_ID = "ID";
        /// <summary>
        /// Table name.
        /// </summary>
        public const string ERROR_TABLE_NAME = XML_ROOT;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the type of the file path.
        /// </summary>
        /// <value>The type of the file path.</value>
        public PathType FilePathType
        {
            get;
            set;
        }

        /// <summary>
        /// the type of logging
        /// </summary>
        /// <value></value>
        public ErrorLogType Type
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the file path.
        /// </summary>
        /// <value>The file path.</value>
        public string FilePath
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether [create file when not found].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [create file when not found]; otherwise, <c>false</c>.
        /// </value>
        [DefaultValue(true)]
        public bool CreateFileWhenNotFound
        {
            get;
            set;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Add new log event
        /// </summary>
        /// <param name="error">Error to add</param>
        public void Add(Exception error)
        {
            XmlDocument document = new XmlDocument();
            XmlElement rootElement;
            if (CheckFile())
            {
                document.AppendChild(document.CreateXmlDeclaration(XML_VERSION, XML_ENCODING, null));
                rootElement = document.CreateElement(XML_ROOT);
                document.AppendChild(rootElement);
            }
            else
                rootElement = document.DocumentElement;

            #region Create Data
            //main node
            XmlElement exceptionElement = document.CreateElement(ERROR_NODE);
            //id
            XmlAttribute idAttribute = document.CreateAttribute(ERROR_ID);
            idAttribute.Value = GetID(ref document);
            exceptionElement.Attributes.Append(idAttribute);
            //children nodes
            //message
            XmlElement messageElement = document.CreateElement(ERROR_MESSAGE);
            messageElement.Value = error.Message.Trim();
            exceptionElement.AppendChild(messageElement);
            //source
            XmlElement sourceElement = document.CreateElement(ERROR_SOURCE);
            sourceElement.Value = error.Source.Trim();
            exceptionElement.AppendChild(sourceElement);
            //stack trace
            XmlElement stackTraceElement = document.CreateElement(ERROR_STACK_TRACE);
            stackTraceElement.Value = error.StackTrace;
            exceptionElement.AppendChild(stackTraceElement);
            //target site
            XmlElement targetSiteElement = document.CreateElement(ERROR_TARGET_SITE);
            targetSiteElement.Value = error.TargetSite.ToString().Trim();
            exceptionElement.AppendChild(targetSiteElement);
            #endregion

            //save the file
            document.Save(FilePathType == PathType.Absolute ? FilePath : System.Reflection.Assembly.GetExecutingAssembly().Location.Trim() + FilePath);
            TraceUtil.Write(TRACING_CATEGORY, "Add : End");
        }

        /// <summary>
        /// Read the log file
        /// </summary>
        /// <returns></returns>
        public DataTable Read()
        {
            TraceUtil.Write(TRACING_CATEGORY, "Read : Start");
            if (CheckFile())
                return null;
            XmlDocument document = new XmlDocument();
            document.Load(FilePathType == PathType.Absolute ? FilePath
                : System.Reflection.Assembly.GetExecutingAssembly().Location.Trim() + FilePath);
            if (!document.HasChildNodes)
                return null;
            DataTable data = CreateSchema();
            foreach (XmlNode node in document.DocumentElement.ChildNodes)
            {
                DataRow error = data.NewRow();
                error[ERROR_ID] = node.Attributes[ERROR_ID].Value.Trim();
                foreach (XmlNode childNode in node.ChildNodes)
                {
                    switch (childNode.Name)
                    {
                        case ERROR_MESSAGE:
                            error[ERROR_MESSAGE] = childNode.Value;
                            break;
                        case ERROR_SOURCE:
                            error[ERROR_SOURCE] = childNode.Value;
                            break;
                        case ERROR_STACK_TRACE:
                            error[ERROR_STACK_TRACE] = childNode.Value;
                            break;
                        case ERROR_TARGET_SITE:
                            error[ERROR_TARGET_SITE] = childNode.Value;
                            break;
                    }
                }
                data.Rows.Add(error);
            }
            TraceUtil.Write(TRACING_CATEGORY, "Read : End");
            return data;
        }

        /// <summary>
        /// Check for FilePath property, 
        /// if the file is not found and CreateFileWhenNotFound is set to be false
        /// it throws an exception
        /// </summary>
        /// <exception cref="Exception">Empty file path property</exception>
        /// <exception cref="Exception">File not found exception</exception>
        /// <returns>True if the file exist</returns>
        private bool CheckFile()
        {
            if (FilePath.Equals(string.Empty))
                new Exception(Resources.FilePathEmpty);
            if ((FilePathType == PathType.Absolute ? !File.Exists(FilePath)
                : !File.Exists(System.Reflection.Assembly.GetExecutingAssembly().Location.Trim() + FilePath)))
                if (!CreateFileWhenNotFound)
                    throw new Exception(Resources.FileNotFound);
                else
                    return true;
            return false;
        }

        /// <summary>
        /// Return the id+1 of the last exception id in the file
        /// </summary>
        /// <param name="document">a refrence to the document file</param>
        /// <returns>id+1</returns>
        private string GetID(ref XmlDocument document)
        {
            if (document == null || document.DocumentType == null || document.GetElementsByTagName(ERROR_NODE).Count == 0)
                return "1";
            else
                return string.Concat(string.Empty, int.Parse(document.GetElementsByTagName(ERROR_NODE)[document.GetElementsByTagName(ERROR_NODE).Count - 1].Attributes[ERROR_ID].Value) + 1);
        }

        /// <summary>
        /// Creates the schema.
        /// </summary>
        /// <returns></returns>
        private DataTable CreateSchema()
        {
            DataTable data = new DataTable(ERROR_TABLE_NAME);
            data.Columns.Add(ERROR_ID);
            data.Columns.Add(ERROR_MESSAGE);
            data.Columns.Add(ERROR_SOURCE);
            data.Columns.Add(ERROR_STACK_TRACE);
            data.Columns.Add(ERROR_TARGET_SITE);
            return data;
        }

        #endregion
    }
}
